package com.ficus.road.maintain.service;

import com.ficus.road.maintain.authority.dto.RoleDTO;
import com.ficus.road.maintain.authority.dto.UserDTO;
import com.ficus.road.maintain.authority.service.RoleService;
import com.ficus.road.maintain.authority.service.UserService;
import com.ficus.road.maintain.authority.utils.SecurityUtils;
import com.ficus.road.maintain.core.model.*;
import com.ficus.road.maintain.dao.*;
import com.ficus.road.maintain.dao.coverage.UnitCoverageDayYTDao;
import com.ficus.road.maintain.service.coverage.YTCoverageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.aggregation.SortOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author jinbiao.he
 * @Since 2021/4/8 上午11:19
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class CarService {

    private final CarDao carDao;
    private final UserService userService;
    private final RoleService roleService;
    private final CarVideoRecordDao carVideoRecordDao;
    private final CompanyDao companyDao;
    private final CarMaintainDao carMaintainDao;
    private final CarTripDao carTripDao;
    private final GpsHistoryDao gpsHistoryDao;
    private final CycleNumService cycleNumService;
    private final CompanyService companyService;

    @Value("${coverage.gps_fix_interval}")
    private Integer interval;

    private Map<String, String> companyNameMap;
    private final YTCoverageService ytCoverageService;

    @PostConstruct
    private void init() {
        List<Company> all = companyDao.findAll();
        companyNameMap = all.stream().collect(Collectors.toMap(Company::getId, item -> StringUtils.isNotEmpty(item.getName()) ? item.getName() : item.getShortName()));
    }

    public String registerCar(Car car) {
        Criteria criteria = Criteria.where("plateNo").is(car.getPlateNo());
        Query query = Query.query(criteria);
        Car one = carDao.findOne(query);
        if (one != null) {
            return one.getId();
        }

        long time = System.currentTimeMillis() / 1000;
        car.setCreationTime(time);
        car.setLastModificationTime(time);
        Car car1 = carDao.insert(car);
        return car1.getId();
    }

    public String findCarIdByPlateNo(String plateNo) {
        Criteria criteria = Criteria.where("plate_no").regex(".*" + plateNo);
        Query query = Query.query(criteria);
        Car one = carDao.findOne(query);
        if (one != null) {
            return one.getId();
        } else {
            return null;
        }
    }

    // 更新车辆坐标信息
    public void updateCarLocation(String carId, GpsLocation gpsLocation, Integer speed, Integer direction) {
        Car car = carDao.fetch(carId);
        car.setGpsLocation(gpsLocation);
        car.setSpeed(speed);
        car.setDirection(direction);
        car.setLastModificationTime(System.currentTimeMillis() / 1000);
        carDao.saveOrUpdate(car);
    }

    // 更新车辆信息
    public void updateCar(String carId, CarDTO carDTO) {
        Car car = carDao.fetch(carId);
        String driver = carDTO.getDriver();
        String principal = carDTO.getPrincipal();
        Long lastMaintenance = carDTO.getLastMaintenance();
        Long nextMaintenance = carDTO.getNextMaintenance();
        if (ObjectUtils.isNotEmpty(driver)) {
            car.setDriver(driver);
        }
        if (ObjectUtils.isNotEmpty(principal)) {
            car.setPrincipal(principal);
        }
        if (ObjectUtils.isNotEmpty(lastMaintenance)) {
            car.setLastMaintenance(lastMaintenance);
        }
        if (ObjectUtils.isNotEmpty(nextMaintenance)) {
            car.setNextMaintenance(nextMaintenance);
        }
//        car.setLastModificationTime(new Date().getTime() / 1000);
        carDao.saveOrUpdate(car);
    }


    public List<CarBasicInfo> getBasicInfo() {
        List<Car> all = carDao.findAll();
        List<Company> companyList = companyDao.findAll();
        Map<String, Set<String>> companyMap = companyList.stream().collect(Collectors.toMap(Company::getId, Company::getUnits));
        List<CarBasicInfo> carBasicInfoList = all.stream().map(item -> {
            CarBasicInfo basicInfo = new CarBasicInfo();
            basicInfo.setId(item.getId());
            basicInfo.setPlate(item.getPlateNo());
            basicInfo.setType(item.getType());
            basicInfo.setCompany(item.getHandleCompany());
            Set<String> units = companyMap.getOrDefault(item.getHandleCompany(), new HashSet<>());
            basicInfo.setUnits(units);
            return basicInfo;
        }).collect(Collectors.toList());
        return carBasicInfoList;
    }

    public List<CarLocation> getCarLocation() {
        List<Car> all = carDao.findAll();
        List<CarLocation> carLocationList = all.stream().map(item -> {
            CarLocation carLocation = new CarLocation();
            carLocation.setId(item.getId());
            carLocation.setPlate(item.getPlateNo());
            carLocation.setLatitude(item.getGpsLocation().getLatitude());
            carLocation.setLongitude(item.getGpsLocation().getLongitude());
            carLocation.setTimestamp(item.getLastModificationTime());
            carLocation.setSpeed(item.getSpeed());
            carLocation.setDirection(item.getDirection());
            return carLocation;
        }).collect(Collectors.toList());
        return carLocationList;
    }

    public List<Car> queryByCompany(String companyCode) {
        Criteria criteria = Criteria.where("handle_company").is(companyCode);
        return carDao.findByQuery(new Query(criteria));
    }


    public List<CarVO> queryCar(Query query, String source) {
        UserDTO userDTO = userService.queryUserByUsername(SecurityUtils.getCurrentUser().getUsername());
        Set<String> roleIdList = userDTO.getRoleIdList();
        List<RoleDTO> roleDTOS = roleService.queryRolesById(roleIdList);
        List<String> roleName = roleDTOS.stream().map(RoleDTO::getRoleName).collect(Collectors.toList());
        //如果不是管理员，只能看到权限列表的车辆;如果是管理员，可以看到所有车辆
        if (!roleName.contains("管理员") && !roleName.contains("道运中心") && !roleName.contains("市道运局")) {
            query.addCriteria(Criteria.where("_id").in(userDTO.getCarIdList()));
        }
        query.with(new Sort(Sort.Direction.DESC, "creationTime"));
        //findByQuery内部有一个getCollectionName()
        List<Car> carList = carDao.findByQuery(query);
        //疑问：carConverter.convert(item)方法详解
        return carList.stream().map(car ->convert(car,source)).collect(Collectors.toList());
    }

    public CarVO getCar(String id, String source) {
        Car car = carDao.fetch(id);
        return convert(car, source);
    }

    public void reportVersion(String carId, String version) {
        Car car = carDao.fetch(carId);
        car.setVersion(version);
        carDao.saveOrUpdate(car);
    }

    public List<CarOutDTO> getCarOutInfo(CarOutQuery carOutQuery) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        Date today = calendar.getTime();
        Date monthAgo = DateUtils.addDays(today, -30);
        List<CarOutDTO> carOutDTOs = new ArrayList<>();
        List<CarVO> carVOs = convertSimplify(new Query());
        List<String> carIdList = carVOs.stream().map(CarVO::getId).collect(Collectors.toList());
        Criteria criteria = Criteria.where("end_time").gte(monthAgo.getTime()).and("car_id").in(carIdList);
        MatchOperation matchOperation = Aggregation.match(criteria);
        SortOperation sortOperation = Aggregation.sort(new Sort(Sort.Direction.ASC, "date"));
        GroupOperation groupOperation = Aggregation.group("car_id", "date").min("start_time").as("start_time").max("end_time").as("end_time");
        Aggregation aggregation = Aggregation.newAggregation(matchOperation, groupOperation, sortOperation);
        List<CarVideoRecord> carVideoRecords = carVideoRecordDao.aggregate(aggregation);
        Map<String, List<CarVideoRecord>> carVideoMap = carVideoRecords.stream().collect(Collectors.groupingBy(CarVideoRecord::getCarId));
        carVideoMap.forEach((carId, carVideoRecordList) -> {
            CarOutDTO carOutDTO = new CarOutDTO();
            List<CarOutDetail> carOutDetails = new ArrayList<>();
            CarVO vo = carVOs.stream().filter(car -> car.getId().equals(carId)).findFirst().get();
            carOutDTO.setPlateNo(vo.getPlateNo());
            carOutDTO.setCompany(companyNameMap.get(vo.getHandleCompany()));
            carVideoRecordList.stream().forEach(video -> {
                CarOutDetail carOutDetail = new CarOutDetail();
                carOutDetail.setDate(convertDate(video.getDate()));
                CarOutPeriod carOutPeriod = CarOutPeriod.builder().startTime(video.getStartTime() / 1000).endTime(video.getEndTime() / 1000).build();
                carOutDetail.setPeriod(carOutPeriod);
                carOutDetails.add(carOutDetail);
            });
            String queryDate = carOutQuery.getDate();
            carOutDTO.setDate(queryDate);
            Set<String> dateSet = carOutDetails.stream().map(CarOutDetail::getDate).collect(Collectors.toSet());
            //1表示出车，2表示未出车
            carOutDTO.setStatus(dateSet.contains(queryDate) ? 1 : 2);
            carOutDTO.setDetail(carOutDetails);
            carOutDTO.setFrequency(carVideoRecordList.size());
            //  前端暂时没用到，先设为false
            carOutDTO.setCurrentStatus(false);
//            carOutDTO.setCurrentStatus(vo.getOnTheRoad().equals(0) ? false : true);
            carOutDTOs.add(carOutDTO);
        });
        //近30天出过车的所有车辆id
        Set<String> carIdListOfMonth = carVideoMap.keySet();
        //该用户看到的车辆，且最近30天未出车的
        carVOs.stream().filter(carVo -> !carIdListOfMonth.contains(carVo.getId())).forEach(car -> {
            CarOutDTO carOutDTO = CarOutDTO.builder().
                    plateNo(car.getPlateNo()).company(companyNameMap.get(car.getHandleCompany()))
                    .date("/").status(2).detail(new ArrayList<>()).frequency(0).build();
            carOutDTOs.add(carOutDTO);
        });
        List<CarOutDTO> res = carOutDTOs;
        if (!carOutQuery.getStatus().equals(0)) {
            res = carOutDTOs.stream().filter(car -> car.getStatus().equals(carOutQuery.getStatus())).collect(Collectors.toList());
        }
        return res;
    }

    public String convertDate(String s) {
        StringBuffer stringBuffer = new StringBuffer(s).insert(4, "-").insert(7, "-");
        return stringBuffer.toString();
    }

    public void carMaintenance(String carId) {
        CarMaintain carMaintain = CarMaintain.builder().carId(carId).createTime(System.currentTimeMillis()).build();
        carMaintainDao.saveOrUpdate(carMaintain);
    }

    public void updateCarTrip(String carId, Double trip) {
        Query query = new Query(Criteria.where("car_id").is(carId));
        query.with(new Sort(Sort.Direction.DESC, "timestamp")).skip(0).limit(1);
        CarTrip carTrip = carTripDao.findOne(query);
        long now = new Date().getTime();
        if (carTrip == null || carTrip.getMile() > trip) {
            //新增
            CarTrip newCarTrip = CarTrip.builder().carId(carId).mile(trip).timestamp(now).build();
            carTripDao.saveOrUpdate(newCarTrip);
        } else {
            //修改
            carTrip.setMile(trip);
            carTrip.setTimestamp(now);
            carTripDao.saveOrUpdate(carTrip);
        }
    }

    /**
     * 车辆指定时间段内没gps轨迹才可以补数据
     *
     * @param carId
     * @return
     */
    public boolean checkGpsManual(String carId) {
        long timestamp = System.currentTimeMillis() / 1000 - interval;
        long count = gpsHistoryDao.count(new Query(Criteria.where("timestamp").gte(timestamp).and("car_id").is(carId).and("is_supplement").exists(Boolean.FALSE)));
        if (count > 0) {
            return false;
        }
        return true;
    }

    public CarVO convert(Car car, String source) {
        CarVO vo = new CarVO();
        vo.setId(car.getId());
        GpsLocation gpsLocation = car.getGpsLocation();
        vo.setGpsLocation(gpsLocation);
        vo.setPlateNo(car.getPlateNo());
        vo.setHandleCompany(car.getHandleCompany());
        vo.setLastMaintenance(car.getLastMaintenance());
        vo.setNextMaintenance(car.getNextMaintenance());
        vo.setBrand(car.getBrand());
        vo.setDriver(car.getDriver());
        vo.setPrincipal(car.getPrincipal());
        String currentCycleNum = cycleNumService.getCurrentCycleNum();
        List<CompanyCoverRateInfoResponse> units;
        if (StringUtils.equals("yitu", source)){
            units = ytCoverageService.getCompanyCoverRates(car.getHandleCompany(), currentCycleNum);
        }else {
            units = companyService.getCompanyCoverRates(car.getHandleCompany(), currentCycleNum);
        }

        vo.setUnits(units);
        long current = System.currentTimeMillis() / 1000;
        long lastModificationTime = car.getLastModificationTime();
        if (current - lastModificationTime < 60) {
            vo.setRunningTime(current - car.getLastStartTime());
        } else {
            vo.setRunningTime(0L);
        }
        //修改日期是当天，则表示当天出车
        Date today = new Date();
        long start = (DateUtils.ceiling(DateUtils.addDays(today, -1), Calendar.DATE)).getTime() / 1000;
        long end = (DateUtils.ceiling(today, Calendar.DATE)).getTime() / 1000;
        if (lastModificationTime > start && lastModificationTime < end) {
            vo.setOnTheRoad(1);
        } else {
            vo.setOnTheRoad(0);
        }
        return vo;
    }

    /**
     * 简化版转换工具（少了覆盖率等其他字段的转化）
     *
     * @param query
     * @return
     */
    public List<CarVO> convertSimplify(Query query) {
        UserDTO userDTO = userService.queryUserByUsername(SecurityUtils.getCurrentUser().getUsername());
        Set<String> roleIdList = userDTO.getRoleIdList();
        List<RoleDTO> roleDTOS = roleService.queryRolesById(roleIdList);
        List<String> roleName = roleDTOS.stream().map(RoleDTO::getRoleName).collect(Collectors.toList());
        //如果不是管理员，只能看到权限列表的车辆;如果是管理员，可以看到所有车辆
        if (!roleName.contains("管理员") && !roleName.contains("道运中心") && !roleName.contains("市道运局")) {
            query.addCriteria(Criteria.where("_id").in(userDTO.getCarIdList()));
        }
        query.with(new Sort(Sort.Direction.DESC, "creationTime"));
        List<Car> carList = carDao.findByQuery(query);
        List<CarVO> vos = carList.stream().map(car -> {
            CarVO vo = new CarVO();
            vo.setId(car.getId());
            vo.setPlateNo(car.getPlateNo());
            vo.setHandleCompany(car.getHandleCompany());
            return vo;
        }).collect(Collectors.toList());
        return vos;
    }
}
